package com.carl.mall.product.service.impl;

import com.carl.mall.product.service.CategoryBrandRelationService;
import com.carl.mall.product.vo.Catalog2VO;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.carl.common.utils.PageUtils;
import com.carl.common.utils.Query;

import com.carl.mall.product.dao.CategoryDao;
import com.carl.mall.product.entity.CategoryEntity;
import com.carl.mall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    @Autowired
    RedissonClient redissonClient;
    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    /**
     * 查询所有的类别数据，然后将数据封装为树形结构，便于前端使用
     * @param params
     * @return
     */
    @Override
    public  List<CategoryEntity> queryPageWithTree(Map<String, Object> params) {

        //1.查询所有的商品分类信息
        List<CategoryEntity> categoryEntities = baseMapper.selectList(null);
        //2.将商品分类信息拆分成树形结构
        //第一步遍历出所有的大类 parent_cid = 0
       List<CategoryEntity> list = categoryEntities.stream().filter(categoryEntity -> categoryEntity.getParentCid()==0)
                .map(categoryEntity -> {
                    //根据大类找到多有的小类 递归方法实现
                    categoryEntity.setChildrens(getCategoryChildrens(categoryEntity,categoryEntities));
                    return categoryEntity;
                }).sorted((entity1,entity2) -> {
                    return (entity1.getSort() == null ? 0 :entity1.getSort()) - (entity2.getSort() == null ? 0 :entity2.getSort());

                }).collect(Collectors.toList());
        // 第二步根据大类找到对应的所有小类
        return list;
    }


    /**
     *  查找该大类下的所有的小类  递归查找
     * @param categoryEntity 某个大类
     * @param categoryEntities 所有的类别数据
     * @return
     */
    private List<CategoryEntity> getCategoryChildrens(CategoryEntity categoryEntity, List<CategoryEntity> categoryEntities) {
        List<CategoryEntity> list = categoryEntities.stream().filter(entity -> {
            // 根据大类找到他的直属的小类
            //注意Long数据比较 不在-128 127之间的数据是 new Long() 对象
            return entity.getParentCid().equals(categoryEntity.getCatId());
            //return entity.getParentCid() == categoryEntity.getCatId();
        }).map(entity -> {
            // 根据这个小类递归找到对应的小小类
            entity.setChildrens(getCategoryChildrens(entity, categoryEntities));
            return entity;
        }).sorted((entity1, entity2) -> {
            return (entity1.getSort() == null ? 0 : entity1.getSort()) - (entity2.getSort() == null ? 0 : entity2.getSort());
        }).collect(Collectors.toList());
        return list;
    }

    /**
     * 逻辑删除
     * @param ids
     */
    @Override
    public void removeCategoryByIds(List<Long> ids) {
         // TODO  1.检查类别数据是否在其他业务中使用

        // 2.批量逻辑删除操作
        baseMapper.deleteBatchIds(ids);

    }

    @CacheEvict(value = "category",allEntries = true)
    @Transactional
    @Override
    public void updateDetail(CategoryEntity entity) {
        // 更新类别名称
        this.updateById(entity);
        if(!StringUtils.isEmpty(entity.getName())){
            // 同步更新级联的数据
            categoryBrandRelationService.updateCatelogName(entity.getCatId(),entity.getName());
            // TODO 同步更新其他的冗余数据
        }
    }

    @Override
    public Long[] findCatelogPath(Long catelogId) {
        List<Long> path = new ArrayList<>();
        List<Long> parentPath = findParentPath(catelogId,path);
        Collections.reverse(parentPath);
        return parentPath.toArray(new Long[parentPath.size()]);
    }


    /**
     * 商城首页分类信息的大类
     * @return
     *
     * 缓存处理
     *   1.存储在redis中的缓存数据的key是默认生成的，缓存名称::SimpleKey[]
     *   2.默认缓存的数据的过期时间-1是永久的
     *   3.缓存的数据，默认使用jdk的序列化机制
     *
     *   改进：
     *   1.生成的缓存数据需要指定自定义的key 可以直接字符串定义也可以通过spel表达式处理：#root.method.name
     *   2.指定缓存数据的存活时间 spring.cache.redis.time-to-live 指定过期时间
     *   3.把缓存的数据保存为json数据
     *
     *
     *   ● 缓存穿透：查询一个null的数据。可以解决 cache-null-values=true
     *   ● 缓存击穿:大量并发进来同时查询一个正好过期的数据。解决方案：分布式锁 sync=true 本地锁
     *   ● 缓存雪崩：大量的key同一个时间点失效。解决方案：添加过期时间 time-to-live=60000 指定过期时间
     */
    @Cacheable(value = {"category"},key = "#root.method.name",sync = true)
    //@Cacheable(value = {"category","product"},key = "'level-01-category'")
    @Override
    public List<CategoryEntity> getLeve1Category() {
        System.out.println("查询数据库操作......");
        long start = System.currentTimeMillis();
        List<CategoryEntity> categoryEntities = baseMapper.queryList();
        System.out.println("查询消耗的时间：" + (System.currentTimeMillis()-start));
        return categoryEntities;
    }

    /**
     * 跟进父编号获取对应的子菜单信息
     * @param list
     * @param parentCid
     * @return
     */
    private List<CategoryEntity> queryByParenCid(List<CategoryEntity> list,Long parentCid){
        List<CategoryEntity> collect = list.stream().filter(item -> {
            return item.getParentCid().equals(parentCid);
        }).collect(Collectors.toList());
        return collect;
    }

    /**
     * 查询二级和三类的信息 并封装成map
     * @return
     */
    @Cacheable(value = "category",key = "#root.methodName")
    @Override
    public Map<String, List<Catalog2VO>> getCatelog2JSON(){
        //获取所有分类的数据
        List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>());
        //想获得大类
        List<CategoryEntity> categoryEntitiesLevelOne = this.queryByParenCid(categoryEntities, 0L);
        // 把一级分类的数据转换为Map容器 key就是一级分类的编号， value就是一级分类对应的二级分类的数据
        Map<String, List<Catalog2VO>> map = categoryEntitiesLevelOne.stream()
                .collect(Collectors.toMap(key -> key.getCatId().toString(), value -> {
                    //根据一级分类的编号查询出二级分类的数据
                    //一级分类的id就是二级分类的父id
                    List<CategoryEntity> categoryEntitiesLevelTwo = this.queryByParenCid(categoryEntities, value.getCatId());
                    List<Catalog2VO> catalog2VOS = null;
                    if (!StringUtils.isEmpty(categoryEntitiesLevelTwo)) {
                        catalog2VOS = categoryEntitiesLevelTwo.stream().map(LevelTwo -> {
                            Catalog2VO catalog2VO =
                                    new Catalog2VO(LevelTwo.getParentCid().toString(), null, LevelTwo.getCatId().toString(), LevelTwo.getName());
                            //根据二级分类的数据找到三级分类的信息
                            List<CategoryEntity> categoryEntitiesLevelThree = this.queryByParenCid(categoryEntities, LevelTwo.getCatId());
                            if (!StringUtils.isEmpty(categoryEntitiesLevelThree)) {
                                List<Catalog2VO.Catalog3VO> catalog3VOList = categoryEntitiesLevelThree.stream().map(three -> {
                                    Catalog2VO.Catalog3VO catalog3VO = new Catalog2VO.Catalog3VO(three.getParentCid().toString(), three.getCatId().toString(),
                                            three.getName());
                                    return catalog3VO;
                                }).collect(Collectors.toList());
                                catalog2VO.setCatalog3List(catalog3VOList);
                            }
                            return catalog2VO;
                        }).collect(Collectors.toList());
                    }
                    ;
                    return catalog2VOS;
                }));

        return map;
    }


//    public Map<String, List<Catalog2VO>> getCatelog2JSONFromDBWithRedisson() {
//        //目的防止缓存击穿
//        String keys = "catalogJSON";
//        //获取分布式锁对象  加锁的时候，这个锁名称一定要注意
//        //商品信息 product-lock product-1001-lock product-1002-lock
//        RLock lock = redissonClient.getLock("catelog2JSON-lock");
//        Map<String, List<Catalog2VO>> data = null;
//            try {
//                lock.lock();
//                // 加锁成功
//                data = getDataForDB(keys);
//            }finally {
//              lock.unlock();
//            }
//            return data;
//        }





//    public Map<String, List<Catalog2VO>> getCatelog2JSONFromDBWithRedisLock() {
//        String keys = "catalogJSON";
//        // 加锁 在执行插入操作的同时设置了过期时间 防止在加锁之后，设置过期时间之前出现中断导致出现死锁
//        String uuid = UUID.randomUUID().toString();
//        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,300,TimeUnit.SECONDS);
//        if(Boolean.TRUE.equals(lock)){
//            Map<String, List<Catalog2VO>> data = null;
//            try {
//                // 加锁成功
//                data = getDataForDB(keys);
//            }finally {
//                //判断锁的key-value值是否相等然后删除key 可以避免在判断和删除中间出现中断情况 导致删除其他请求的锁
//                String srcipts = "if redis.call('get',KEYS[1]) == ARGV[1]  then return redis.call('del',KEYS[1]) else  return 0 end ";
//                // 通过Redis的lua脚本实现 查询和删除操作的原子性
//                stringRedisTemplate.execute(new DefaultRedisScript<Long>(srcipts,Long.class)
//                        ,Arrays.asList("lock"),uuid);
//            }
//            return data;
//        }else{
//            // 加锁失败 让他重新进入这个方法  重新获取锁
//            // 休眠+重试
//            // Thread.sleep(1000);
//            return getCatelog2JSONFromDBWithRedisLock();
//        }
//
//
//
//    }


//    /**
//     * 从数据库中的查询操作 查询二级和三类的信息 并封装成map  从数据库查询
//     * @param keys
//     * @return
//     */
//    private Map<String, List<Catalog2VO>> getDataForDB(String keys) {
//        //确认缓存中是否有数据
//        // 先去缓存中查询有没有数据，如果有就返回，否则查询数据库
//        // 从Redis中获取分类的信息
//        String catalogJSON = stringRedisTemplate.opsForValue().get(keys);
//        if (!StringUtils.isEmpty(catalogJSON)) {
//            // 说明缓存命中
//            // 表示缓存命中了数据，那么从缓存中获取信息，然后返回
//            return JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2VO>>>() {
//            });
//        }
//        System.out.println("-----------》查询数据库操作");
//        //获取所有分类的数据
//        List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>());
//        //想获得大类
//        List<CategoryEntity> categoryEntitiesLevelOne = this.queryByParenCid(categoryEntities, 0L);
//        // 把一级分类的数据转换为Map容器 key就是一级分类的编号， value就是一级分类对应的二级分类的数据
//        Map<String, List<Catalog2VO>> map = categoryEntitiesLevelOne.stream()
//                .collect(Collectors.toMap(key -> key.getCatId().toString(), value -> {
//                    //根据一级分类的编号查询出二级分类的数据
//                    //一级分类的id就是二级分类的父id
//                    List<CategoryEntity> categoryEntitiesLevelTwo = this.queryByParenCid(categoryEntities, value.getCatId());
//                    List<Catalog2VO> catalog2VOS = null;
//                    if (!StringUtils.isEmpty(categoryEntitiesLevelTwo)) {
//                        catalog2VOS = categoryEntitiesLevelTwo.stream().map(LevelTwo -> {
//                            Catalog2VO catalog2VO =
//                                    new Catalog2VO(LevelTwo.getParentCid().toString(), null, LevelTwo.getCatId().toString(), LevelTwo.getName());
//                            //根据二级分类的数据找到三级分类的信息
//                            List<CategoryEntity> categoryEntitiesLevelThree = this.queryByParenCid(categoryEntities, LevelTwo.getCatId());
//                            if (!StringUtils.isEmpty(categoryEntitiesLevelThree)) {
//                                List<Catalog2VO.Catalog3VO> catalog3VOList = categoryEntitiesLevelThree.stream().map(three -> {
//                                    Catalog2VO.Catalog3VO catalog3VO = new Catalog2VO.Catalog3VO(three.getParentCid().toString(), three.getCatId().toString(),
//                                            three.getName());
//                                    return catalog3VO;
//                                }).collect(Collectors.toList());
//                                catalog2VO.setCatalog3List(catalog3VOList);
//                            }
//                            return catalog2VO;
//                        }).collect(Collectors.toList());
//                    }
//                    ;
//                    return catalog2VOS;
//                }));
//        //没有的话存到缓存里下次查询就有了
//        // 表示缓存命中了数据，那么从缓存中获取信息，然后返回
//        if (map == null) {
//            // 那就说明数据库中也不存在  防止缓存穿透
//            stringRedisTemplate.opsForValue().set(keys, "1", 5, TimeUnit.SECONDS);
//        } else {
//            // 从数据库中查询到的数据，我们需要给缓存中也存储一份
//            // 防止缓存雪崩
//            String json = JSON.toJSONString(map);
//            stringRedisTemplate.opsForValue().set(keys, json, new Random().nextInt(10), TimeUnit.HOURS);
//        }
//        return map;
//    }

//    /**
//     * 查询二级和三类的信息 并封装成map  从数据库查询（弃用）
//     * @return
//     */
//    public Map<String, List<Catalog2VO>> getCatelog2JSONFromDB() {
//        String keys = "catalogJSON";
//        //对数据库查询进行同步代码块加锁
//        synchronized (this) {
//
//            //确认缓存中是否有数据
//            // 先去缓存中查询有没有数据，如果有就返回，否则查询数据库
//            // 从Redis中获取分类的信息
//            return getDataForDB(keys);
//
//        }
//
//
//    }

    public List<Long> findParentPath(Long catelogId,List<Long> pa){
        pa.add(catelogId);
        //Long parentCid = this.getById(catelogId).getParentCid();
        Long parentCid = baseMapper
                .selectOne(new QueryWrapper<CategoryEntity>().eq("cat_id", catelogId)).getParentCid();
        if (parentCid != 0){
            findParentPath(parentCid, pa);
        }
        return pa;

    }


}